﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.IO;
using System.Xml;

namespace RTPtest1
{
    public struct ParsedData
    {
       public String type;
       public double value;

       public ParsedData(String s, double val)
       {
           type = String.Copy(s);
           value = val;
       }
    }
    
    
    public partial class Form1 : Form
    {
        private Object pollingLock = new Object();
        Boolean polling = false;
        static byte[] buffer;
        static int size;
        static Socket s;
        static DataSet nodes = new DataSet();
        static DataTable nodetable = nodes.Tables.Add("Node Data");
        static MemoryStream motepacket;
        static XmlTextReader xmlreader;
        IAsyncResult socketasynresult;
        Process bash;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //initializations for startup
            //creating data table, setting primary key
            DataColumn nodeid = nodetable.Columns.Add("Node ID", typeof(Int32));
            DataColumn[] columns = new DataColumn[1];
            columns[0] = nodetable.Columns["Node ID"];
            nodetable.PrimaryKey = columns;
            //setting initial node table column settings; in future, this will need to be done from file
            nodetable.Columns.Add("Voltage", typeof(Double));
            nodetable.Columns.Add("Humidity", typeof(Double));
            nodetable.Columns.Add("Hum-Temp.", typeof(Double));
            nodetable.Columns.Add("ADC0", typeof(Double));
            nodetable.Columns.Add("ADC1", typeof(Double));
            nodetable.Columns.Add("ADC2", typeof(Double));
            nodetable.Columns.Add("DIGI0", typeof(Double));
            nodetable.Columns.Add("DIGI1", typeof(Double));
            nodetable.Columns.Add("DIGI2", typeof(Double));
            nodetable.Columns.Add("Time", typeof(string));
            nodetable.Columns.Add("Group ID", typeof(Int32));
            moteTable.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
            moteTable.DataSource = nodetable;
            this.moteTable.Columns["Group ID"].Visible = false;
            this.moteTable.Columns["Node ID"].MinimumWidth = 75;
            this.moteTable.Columns["Time"].MinimumWidth = 125;
        }

        public delegate void printmessage(String s);
        public void printToMessageConsole(String s)
        {
            messageConsole.AppendText(s);
        }

        /*
         *\brief This method receives a byte[] of a new mote packet, and returns an object array of parsed data
         *
         *This method receives a byte[] of a mote packet.
         *The passed document is expected to be in converted format.
         *If the document is invalid, the method returns a null value.
         *@param motepacket byte array of the motepacket in XML format
         *@return ParsedData array containing parsed data, or null if invalid document
         */
        public ParsedData[] MotePacketParser(byte[] xmlpacket)
        {
            //return null if a null or zero length byte array (no data)
            if (xmlpacket == null || xmlpacket.Length == 0)
                throw new MotePacketException("Empty Packet\n");
            try
            {
                motepacket = new MemoryStream(xmlpacket);
                xmlreader = new XmlTextReader(motepacket);
                //find number of elements
                int i = 0;
                while (!xmlreader.EOF)
                {
                    if (xmlreader.ReadToFollowing("ParsedDataElement"))
                        i++;
                    else //no more parsed data elements found
                        break;
                }
                xmlreader.Close();
                motepacket.Close();

                if (i == 0)	//no elements found or bad packet
                    throw new MotePacketException("No elements found in packet\n");
                //create array to store data
                ParsedData[] tempdata = new ParsedData[i];

                //reset XML reader
                motepacket = new MemoryStream(xmlpacket);
                xmlreader = new XmlTextReader(motepacket);

                int j;
                double tempvalue;
                String temptype;
                
                for (j = 0; j < i; j++)
                {
                    temptype = null;
                    tempvalue = 0;
                    if (xmlreader.ReadToFollowing("Name"))
                    {
                        if (xmlreader.Read())
                        {
                            temptype = xmlreader.ReadContentAsString();
                            if (xmlreader.ReadToFollowing("ConvertedValue"))
                            {
                                if (xmlreader.Read())
                                    tempvalue = xmlreader.ReadContentAsDouble();
                                else //no data inside "Converted Value" element
                                    throw new MotePacketException("Dirty packet\n");
                            }
                            else	//no "Converted Value" element found
                                throw new MotePacketException("Dirty packet\n");
                        }
                        else //no data inside "Name" element
                            throw new MotePacketException("Dirty packet\n");
                    }
                    else
                        throw new MotePacketException("Dirty packet\n");

                    if (temptype == null) //packet is bad somehow
                        return null;
                    else
                        tempdata[j] = new ParsedData(temptype, tempvalue);
                }
                return tempdata;
            }
            catch (Exception ex)
            {
                printmessage showerror = printToMessageConsole;
                messageConsole.Invoke(showerror, ex.Message + "\n");
                return null;
            }
			
			finally
            {
                if (motepacket != null)
                    motepacket.Close();
                if (xmlreader != null)
                    xmlreader.Close();
			}
        }
       
        private void DataPolling(IAsyncResult result)
        {
            if (s.Connected)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    messageConsole.AppendText("Connected!\n");
                });
            }
            try
            {
                //now connected to xserve; prepare to receive data
                while (polling)
                {
                    if (s.Connected)
                    {
                        buffer = new byte[4];

                        s.Receive(buffer, 0, 4, SocketFlags.None);
                        
                        size = BitConverter.ToInt32(buffer, 0);

                        if (size == 0)
                        {
                            continue;
                        }
                        buffer = new byte[size];
                        s.Receive(buffer, 0, size, SocketFlags.None);

                        //get parsed data into an array
                        ParsedData[] newdata = MotePacketParser(buffer);

                        if (newdata == null || newdata.Length != 16) //skipping packet if not size 16; temporary
                        {
                            continue; //bad data, skip packet
                        }
                        else
                        {
                            object[] temprow = new object[12];
                            temprow[0] = newdata[1].value;
                            temprow[1] = newdata[7].value;
                            temprow[2] = newdata[8].value;
                            temprow[3] = newdata[9].value;
                            temprow[4] = newdata[10].value;
                            temprow[5] = newdata[11].value;
                            temprow[6] = newdata[12].value;
                            temprow[7] = newdata[13].value;
                            temprow[8] = newdata[14].value;
                            temprow[9] = newdata[15].value;
                            temprow[10] = DateTime.Now.ToString();
                            temprow[11] = newdata[3].value;
                            NodeArgs args = new NodeArgs();
                            args.newrow = temprow;
                            //add data to table
                            DataHandler ProcessData = AddData;
                            this.moteTable.Invoke(ProcessData, args);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                printmessage showerror = printToMessageConsole;
                messageConsole.Invoke(showerror, ex.Message + "\n");
            }
            finally
            {
                if(s != null)
                    s.Close();
                this.BeginInvoke((MethodInvoker)delegate
                {
                    messageConsole.AppendText("Disconnected!\n");
                });

                this.BeginInvoke((MethodInvoker)delegate
                {
                    connectToolStripMenuItem.Enabled = true;
                    disconnectToolStripMenuItem.Enabled = false;
                });
            }
        }
        public delegate void DataHandler(NodeArgs e);
        public void AddData(NodeArgs e)
        {
            nodetable.BeginLoadData();
            nodetable.LoadDataRow(e.newrow, true);
            nodetable.EndLoadData();
            this.moteTable.Refresh();
        }
        
        private void connectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            disconnectToolStripMenuItem.Enabled = true;
            s = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream, ProtocolType.Tcp);
            s.SendTimeout = 5000;
            s.ReceiveTimeout = 5000;
            lock (pollingLock)
            {
                polling = true;
            }
            AsyncCallback callBack = new AsyncCallback(DataPolling);
            s.Blocking = true;

            messageConsole.AppendText("Connecting To Xserve...\n");

            try
            {
                //attempt asynchronous polling
                socketasynresult = s.BeginConnect(IPAddress.Parse("127.0.0.1"), 9005, callBack, s);
                connectToolStripMenuItem.Enabled = false;
                nodepopup popup = new nodepopup();
                popup.ShowDialog();
            }

            catch (Exception ex)
            {
                printmessage showerror = printToMessageConsole;
                messageConsole.Invoke(showerror, ex.Message + "\n");
                disconnectToolStripMenuItem.Enabled = false;
                connectToolStripMenuItem.Enabled = true;
                if (s != null)
                    s.Close();
            }
            finally
            {
            }
        }

        private Process launchXserve()
        {
            Process cygwin = new Process();
            //this will eventually have to change to be in the moteview directory
            cygwin.StartInfo.FileName = "C:\\cygwin\\Cygwin.bat";
            //cygwin.StartInfo.Arguments = "--login -i ";

            cygwin.StartInfo.RedirectStandardError = true;
            cygwin.StartInfo.RedirectStandardInput = true;
            cygwin.StartInfo.RedirectStandardOutput = true;
            cygwin.StartInfo.CreateNoWindow = true;
            cygwin.StartInfo.UseShellExecute = false;
            cygwin.StartInfo.ErrorDialog = false;

            //start process!
            cygwin.Start();

            //create delegates for asynchronous calls during output/error received
            DataReceivedEventHandler errorEventHandler = new DataReceivedEventHandler(ErrorDataReceived);
            DataReceivedEventHandler outEventHandler = new DataReceivedEventHandler(OutDataReceived);

            //assign delegates
            cygwin.OutputDataReceived += outEventHandler;
            cygwin.ErrorDataReceived += errorEventHandler;

            //begin asynchronous monitoring
            cygwin.BeginErrorReadLine();
            cygwin.BeginOutputReadLine();

            //start xserve!
            cygwin.StandardInput.WriteLine("cd \"C:/Program Files (x86)/Crossbow/Moteview/xserve2/bin\"");
            cygwin.StandardInput.WriteLine("./xserve -device=com6 -xmlc -xmlport=9005");
            
            return cygwin;
        }

        private void ErrorDataReceived(object sender, DataReceivedEventArgs errorLine)
        {
        }
        
        private void OutDataReceived(object sender, DataReceivedEventArgs outLine)
        {
            messageConsole.Invoke(new printmessage(this.printToMessageConsole), new object[] { outLine.Data + "\n"});
        }

        private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            lock (pollingLock)
            {
                polling = false;
            }
        }

        private void tablePage_Click(object sender, EventArgs e)
        {

        }

        private void launchXServeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bash = launchXserve();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            lock (pollingLock) //destroy data polling
            {
                polling = false;
            }

            /*another option -- XMLRPC for shutdown
            //have to instantiate a nodepopup, since this is where the function lies
            nodepopup popup = new nodepopup();
            popup.xserve_Shutdown();
            popup.Dispose();
            */
            //kills all instances of xserve if XMLRPC fails
            Process[] localByName = Process.GetProcessesByName("xserve");
            for (int i = 0; i < localByName.Length; i++)
            {
                localByName[i].Kill();
            }

        }

        private void moteTable_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == (int)this.moteTable.Columns["Node ID"].Index)
            {
                nodepopup popup = new nodepopup();
                popup.node_id = (int)this.moteTable["Node ID", e.RowIndex].Value;
                popup.group_id = (int)this.moteTable["Group ID", e.RowIndex].Value;
                popup.ShowDialog();
            }
        }

        private void abortXServeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Process[] localByName = Process.GetProcessesByName("xserve");
            for (int i = 0; i < localByName.Length; i++)
            {
                localByName[i].Kill();
            }
        }
    }
}
